using ElasticReflection.Exceptions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using ElasticReflection.Helpers;

namespace ElasticReflection
{
    /// <summary>
    /// Wrapper for Type class to simplify functionality. 
    /// </summary>
    public class ElasticType
    {
        internal Type Base;
        Type[] Generics;

        object _Instance;
        public object Instance
        {
            get
            {
                return _Instance;
            }
            set
            {
                if (value != null)
                    if (value.GetType().IsAssignableFrom(Base.DeclaringType))
                        throw new Exception("Instance type does not match the property type");
                _Instance = value;
            }
        }


        private ElasticType() { }



        public IEnumerable<ElasticMethod> Method(string Name, params Type[] Generics)
        {
            var Result = Base.GetMembers().Where(m => m.Name == Name && m is MethodInfo).Cast<MethodInfo>();
            if (Result.Count() == 0) throw new ArgumentException("Method with name not found.", "Name");

            if (Generics != null && Generics.Length > 0)
            {
                Result = Result.Where(m => m.IsGenericMethod && m.CanMakeGenericMethod(Generics)).Select(m => m.MakeGenericMethod(Generics));
                if (Result.Count() == 0) throw new InvalidGenericsException();
            }

            return Result.Select(m => new ElasticMethod(this, m));
        }

        public static ElasticType Create(Type Base, object Instance, params Type[] Generics)
        {
            ElasticType result = new ElasticType();
            if (Generics != null && Generics.Length > 0)
            {
                result.Generics = Generics;
                result.Base = Base.MakeGenericType(Generics);
            }
            else
            {
                result.Base = Base;
            }
            result.Instance = Instance;
            return result;
        }

        #region IsOfType
        public bool IsOfType(Type BaseType)
        {
            return Base.IsAssignableFrom(Base);
        }
        public bool IsOfType<T>()
        {
            return IsOfType(typeof(T));
        }
        #endregion


        public ElasticProperty Property(string Name)
        {
            PropertyInfo propertyInfo = Base.GetProperty(Name);
            if (propertyInfo == null)
            {
                throw new Exception("Property not found");
            }
            return new ElasticProperty(this, propertyInfo);
        }
        public ElasticEvent Event(string Name)
        {
            EventInfo eventInfo = Base.GetEvent(Name);
            if (eventInfo == null)
            {
                throw new Exception("Event not found");
            }
            return new ElasticEvent(this, eventInfo);
        }
    }
}
